/* This file contains functions which are used to implement the authentication */
#include <libnet.h>
#include <pcap.h>
#include <stdio.h>
#include "global.h"
#include "myerr.h"
#include "linkthread.h"
#include "sendpacket.h"
#include "configfile.h"

extern Config_Info *config_info;

#define TRUE 1
#define FALSE 0
unsigned char	l_localMAC[6];//本机的MAC地址
unsigned char   l_destMAC[6];//服务器的MAC地址.

extern int state_global;
libnet_t *l=NULL;
pcap_t	*p=NULL;
int	p_fd;

static int init_daemon(void);

void
init(void)
{
	char	l_errbuf[LIBNET_ERRBUF_SIZE];
	char	filter_buf[256];
	struct	libnet_ether_addr *l_ether_addr;
	struct	bpf_program  filter_code;
	u_int32_t p_netmask;
	char	p_errbuf[PCAP_ERRBUF_SIZE];

	load_config();
	sprintf(filter_buf,"ifconfig %s up",config_info->interface);
	system(filter_buf);
	if((l=libnet_init(LIBNET_LINK, config_info->interface,l_errbuf))==NULL)
		err_quit("libnet_init: %s\n", l_errbuf);

	if((p=pcap_open_live(config_info->interface,65536,0, 500, p_errbuf))==NULL){
		err_msg("pcap_open_live: %s\n",p_errbuf);
	}
	p_fd=pcap_fileno(p); //we can pselect() it in the following code.

	if((l_ether_addr=libnet_get_hwaddr(l))==NULL){
		err_msg("unable to get local mac address :%s\n",libnet_geterror(l));
	}
	memcpy(l_localMAC,l_ether_addr,sizeof(l_localMAC));

	snprintf(filter_buf,sizeof(filter_buf),FILTER_STR, l_localMAC[0],l_localMAC[1],
		l_localMAC[2],l_localMAC[3],l_localMAC[4],l_localMAC[5]);
	if(pcap_compile(p, &filter_code,filter_buf, 0, p_netmask)==-1){
		err_msg("pcap_compile(): %s", pcap_geterr(p));  
	}
	if(pcap_setfilter(p, &filter_code)==-1){
		err_msg("pcap_setfilter(): %s", pcap_geterr(p)); 
	}
	pcap_freecode(&filter_code); // avoid  memory-leak
	
}

void
release_network()
{
	pcap_close(p); 
	libnet_destroy(l);
}


int
link_thread()
{
	int ret;
	fd_set read_set;
	struct pcap_pkthdr *pkt_hdr;
	const u_char	*pkt_data;
	int	isFirstPacketFromServer=1;
	int count_search_server=0;
	int count_send_name=0;
	int count_send_password=0;
	int count_wait_for_refresh=0;
	int count_logout=0;
	struct timespec timeout;
	//char 	buffer[16];
	enum 
	{
		SEARCHING_SERVER,
		SEND_NAME,
		SEND_PASSWORD,
		ONLINE,
		UNLINK
	}linkstate;
	timeout.tv_sec=1;timeout.tv_nsec=0;
	linkstate=SEARCHING_SERVER;
	SendFindServerPacket(l);
	while(TRUE)
	{

/* 获取服务器数据 */
 		FD_ZERO(&read_set);
		FD_SET(p_fd,&read_set);
		ret=pselect(p_fd+1,&read_set,NULL,NULL,&timeout,NULL);
		switch(ret)
		{
		case -1:
			puts("error occur when pselect!");
			return(-1);
			break;
		case 0:		/* 没有收到数据包 */ 
			switch (linkstate)
			{
				/* 寻找服务器阶段 */  
			case SEARCHING_SERVER:
				if(++count_search_server >= config_info->response_time_out)
				{
					un_link();
					return 1;
				}
				else
				{
					SendFindServerPacket(l);
					continue;
				}
				/* 发送用户名阶段 */  
			case SEND_NAME:
				if(++count_send_name >= config_info->retry_times)
				{
					count_send_name=0;
					SendFindServerPacket(l);
				}
				SendNamePacket(l,pkt_data);		
				continue;
				/* 发送密码阶段 */		 
			case SEND_PASSWORD:
				if(++count_send_name >= config_info->retry_times)
				{
					puts("send password packet time out!");
					count_send_password=0;
					SendFindServerPacket(l);
				}
				SendPasswordPacket(l,pkt_data);
				continue;
				/* 认证成功,在线阶段 */ 
			case ONLINE:
				if(++count_wait_for_refresh >= config_info->refresh_time_out)
				{
					if(config_info->auto_retry)
						linkstate=SEARCHING_SERVER;
					else
						;
				}
				else
					continue;
				continue;
			case UNLINK:
				if(++count_logout >= config_info->retry_times)
				{
					un_link();
					return 0;
				}
				else
					continue;
			default:
				continue;
			}
		}
		if((pcap_next_ex(p,&pkt_hdr,&pkt_data))!=1) continue;

//通过比较MAC地址来确定这个数据包是不是服务器发来的,是则分析之,否则忽略掉
		if ((!isFirstPacketFromServer)&&(memcmp(l_destMAC,pkt_data+6,6)!=0)) continue;

		switch( pkt_data[0x12] )	//分析EAP包类型
		{
		case 0x01:        //表示请求
			switch( pkt_data[0x16] )
			{
                        case 0x01:   //要求发送用户名
				if (linkstate == ONLINE)//在线状态, 服务器要求更新连接(约每两分钟更新一次)
					count_wait_for_refresh=0;	
				//置等待更新时间为零
				 
				else if(linkstate == SEARCHING_SERVER){	//正在联接
					count_search_server=0;
                                      	linkstate=SEND_NAME;
                                      	if (isFirstPacketFromServer) //获得服务器的MAC地址
                                        {
						memcpy( l_destMAC, pkt_data+6, 6);
						isFirstPacketFromServer=0;
					}
					++count_send_name;
				}
                                (void)SendNamePacket(l, pkt_data);
				break;
                        case 0x52:   //发送密码(经过加密算法处理)
				if(linkstate != SEND_NAME)continue;
				count_send_name=0;
				linkstate=SEND_PASSWORD;
				++count_send_password;
				(void)SendPasswordPacket(l, pkt_data);
 				break;
			}
			break;
		case 0x03:         //认证成功
			if(linkstate != SEND_PASSWORD) continue;
			count_send_password=0;
			linkstate=ONLINE;
			//调用ifup来获得网络配置,这需要3到10秒的时间
			/*
			sprintf(buffer,"ifconfig %s down",config_info->interface);
			system(buffer);
			sprintf(buffer,"ifconfig %s up",config_info->interface);
			system(buffer); */
			system("dhclient"); //试试这个不知道可不可以解决无法重启网卡的问题。
			//大功告成,终于连好了
			init_daemon();
			break;
		case 0x04:        //连接失败了
                   
			switch (linkstate)
			{
			case SEARCHING_SERVER:
				//不在上网时段?
				fputs("Server freeze your connectiong\n", stdout);
				break;
			case ONLINE:
				//服务器切断连接(没钱啦?到断网时间了?)
				fputs("Server cut down your connectiong!!!\n", stderr);
				break;
			case SEND_NAME:
			case SEND_PASSWORD:
				fputs("Username of Password error!!!\n", stdout);
				//用户名错啦?密码错啦?没钱啦?
				break;
			case UNLINK:
				break;
			}//end switch
			un_link();
			return 1;
		}// end switch
	}//end while
}

void 
un_link()
{
	SendEndCertPacket(l);
	release_network();
	free_config();
	exit(EXIT_SUCCESS);
}

int
init_daemon(void)
{
    pid_t pid;
    
    if ( (pid = fork()) < 0)     /* fork two process */
        return -1;
    else if (pid)                /* grandfa goes bye bye */
        exit(EXIT_SUCCESS);

    if ( setsid() < 0)
        return -1;

    signal(SIGHUP, SIG_IGN);
    if ( (pid = fork()) < 0)
        return -1;
    else if(pid)                /* father goes bye bye too */
        exit(EXIT_SUCCESS);


    close(0);
    close(1);
    if (open("/dev/null", O_RDONLY) == -1)
	    return -1;
    if (open("/tmp/jerry.log", O_WRONLY | O_APPEND | O_CREAT, S_IWUSR | S_IRUSR) == -1)
	    return -1;
    
    return 0;
}

